#include "LoadingState.h"
#include "VisualizerState.h"
#include "Overseer.h"
#include "GUIManager.h"
#include "InputManager.h"
#include "MaterialDictionary.h"
#include "PlantDictionary.h"
#include "Settings.h"

#include "proto/Map.pb.h"
#include "proto/Block.pb.h"
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/gzip_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>

using namespace google::protobuf::io;

#include "PolyVoxGenerator.h"

#include <climits>
#include <iostream>
#include <fstream>

LoadingState::LoadingState(Overseer *o, std::string f) : State(o), filename(f), progress(0)
{
    name = "Loading";
}

void LoadingState::initialise()
{
    overseer->getGUIManager()->executeScript("loadingScreen.lua");
    //overseer->getInputManager()->releaseMouse();
    loaderThread = boost::thread(&LoadingState::loadMap, this);
}

void LoadingState::pause()
{
}

void LoadingState::resume()
{
}

void LoadingState::update(double timeSinceLastFrame)
{
    //should only need to trigger CEGUI events for the lua loader scripts to handle
}

void LoadingState::loadMap()
{
    Settings *settings = Settings::getSingletonPtr();

    bool drawLiquids = settings->getSetting<bool>("General.Liquids");
    bool drawPlants = settings->getSetting<bool>("General.Plants");

    setStatus(0.0f,"Initializing resource groups...");
    Ogre::ResourceGroupManager::getSingletonPtr()->initialiseAllResourceGroups();

    setStatus(0.01f, "Reading map file...");
    std::ifstream input_file(filename.c_str(), std::ios::in | std::ios::binary);
    if (!input_file.is_open())
    {
        throwError("Overseer couldn't open the selected file! Please try again.");
        return;
    }

    ZeroCopyInputStream *raw_input = new IstreamInputStream(&input_file);
    GzipInputStream *zip_input = new GzipInputStream(raw_input);
    CodedInputStream *coded_input = new CodedInputStream(zip_input);

    coded_input->SetTotalBytesLimit(INT_MAX,-1); //silly protobuf, we're not reading large messages, we're just reading a lot of them

    uint32_t header;
    dfproto::Map map;
    uint32_t message_size;
    CodedInputStream::Limit limit;
    float p = 0.01;
    float pstep;

    generator = new PolyVoxGenerator();
    MaterialDictionary *matdict = new MaterialDictionary();
    PlantDictionary *plantdict = new PlantDictionary();

    if (!coded_input->ReadLittleEndian32(&header) || header!=0x50414DDF)
    {
        throwError("The selected file does not contain the proper header! Please select another file.");
        delete matdict;
        delete plantdict;
        delete generator;
        generator = 0;
        goto cleanup;
    }

    coded_input->ReadVarint32(&message_size);
    limit = coded_input->PushLimit(message_size);

    if (!map.ParseFromCodedStream(coded_input))
    {
        throwError("Couldn't read map information from selected file -- it may be corrupt.");
        delete matdict;
        delete plantdict;
        delete generator;
        generator = 0;
        goto cleanup;
    }
    coded_input->PopLimit(limit);

    generator->setMapSize(map.x_size(), map.y_size(), map.z_size());

    setStatus(0.02f, "Reading material information...");

    for (int i=0; i < map.inorganic_material_size(); i++)
    {
        const dfproto::Material& mat = map.inorganic_material(i);
        matdict->addInorganic((uint32_t)mat.index(), mat.name());
    }

    for (int i=0; i < map.organic_material_size(); i++)
    {
        const dfproto::Material& mat = map.organic_material(i);
        matdict->addOrganic((uint32_t)mat.index(), mat.name());
    }

    matdict->loadColors("xml/colors");
    generator->setMaterialDictionary(matdict);

    setStatus(0.04f, "Reading plant information...");
    plantdict->loadPlants("xml/plants");
    generator->setPlantDictionary(plantdict);

    pstep = 0.9f/(map.x_size()*map.y_size()*map.z_size());

    try
    {
        while(!coded_input->ExpectAtEnd())
        {
            boost::this_thread::interruption_point();
            if (!coded_input->ReadVarint32(&message_size)) break;
            limit = coded_input->PushLimit(message_size);
            dfproto::Block block;
            if (!block.ParsePartialFromCodedStream(coded_input))
            {
                throwError("Error reading a block from selected file -- it may be corrupt.");
                delete matdict;
                delete plantdict;
                delete generator;
                generator = 0;
                break;
            }
            std::ostringstream ss;
            ss << "Loading z-level " << block.z();
            setStatus(p+=pstep,ss.str());

            std::set<std::pair<uint32_t,uint32_t> > actualPlants;

            for (int i=0; i < block.tile_size(); i++)
            {
                const dfproto::Tile& tile = block.tile(i);
                generator->setTile(block.y()*16+tile.y(), block.x()*16+tile.x(), block.z(), (GeometryGenerator::TileType)tile.type(), (GeometryGenerator::TileMaterialType)tile.tile_material(), tile.material_index(), tile.material_type());
                if (tile.flow_size() > 0 && drawLiquids) generator->addLiquid(block.y()*16+tile.y(), block.x()*16+tile.x(), block.z(), (GeometryGenerator::LiquidType)tile.liquid_type(), tile.flow_size());

                if (drawPlants && (tile.type() == dfproto::Tile::SAPLING || tile.type() == dfproto::Tile::TREE || tile.type() == dfproto::Tile::SHRUB)) actualPlants.insert(std::pair<uint32_t,uint32_t>(tile.y(),tile.x()));
            }
            if (drawPlants)
                for (int i=0; i < block.plant_size(); i++)
                {
                    const dfproto::Plant& plant = block.plant(i);
                    if (actualPlants.find(std::pair<uint32_t,uint32_t>(plant.y(),plant.x())) != actualPlants.end())
                        generator->addTree(block.y()*16+plant.y(), block.x()*16+plant.x(), block.z(), plant.material(), plant.is_shrub());
                }

            coded_input->PopLimit(limit);
        }
    }
    catch (boost::thread_interrupted)
    {
    }
    setStatus(0.95,"Preparing the scene...");

    overseer->changeState(new VisualizerState(overseer,generator));

cleanup:
    delete coded_input;
    delete zip_input;
    delete raw_input;
}

void LoadingState::setStatus(float p, std::string s)
{
    boost::mutex::scoped_lock lock(statusMutex);
    statusMessage = s;
    progress = p;
}

void LoadingState::throwError(std::string e)
{
    boost::mutex::scoped_lock lock(errorMutex);
    errorMessage = e;
}

float LoadingState::getProgress()
{
    boost::mutex::scoped_lock lock(statusMutex);
    return progress;
}

std::string LoadingState::getStatus()
{
    boost::mutex::scoped_lock lock(statusMutex);
    return statusMessage;
}

std::string LoadingState::getError()
{
    boost::mutex::scoped_lock lock(errorMutex);
    return errorMessage;
}

void LoadingState::exit()
{
    if (generator)
    {
        generator->prepareScene();
        setStatus(1.0,"Done!");
    }
    loaderThread.interrupt();
    loaderThread.join();
}

bool LoadingState::keyPressed(const OIS::KeyEvent &evt)
{
    if (evt.key==OIS::KC_ESCAPE) overseer->exit();
    return true;
}
bool LoadingState::keyReleased(const OIS::KeyEvent &evt)
{
    return true;
}
bool LoadingState::mouseMoved(const OIS::MouseEvent &evt)
{
    return true;
}
bool LoadingState::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id)
{
    return true;
}
bool LoadingState::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id)
{
    return true;
}
